// TODO: CODE REVIEW & CLEANUP!
//
// Shadow.cs
//
// Implements Shadow and related types.
//

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using HomeUX.ClientControlSupport;

namespace HomeUX.Controls
{

// Displays a shadow underneath given rectangular content.

[TemplatePart(Name = ShadowRightColumnName, Type = typeof(ColumnDefinition))]
[TemplatePart(Name = ShadowBottomRowName, Type = typeof(RowDefinition))]
[TemplatePart(Name = ShadowParentName, Type = typeof(Panel))]
public class Shadow : ContentControl, IControlHelperConnector
{
    const string ShadowRightColumnName = "ShadowRightColumn";
    const string ShadowBottomRowName = "ShadowBottomRow";
    const string ShadowParentName = "ShadowParent";

    public static readonly DependencyProperty ShadowOverhangProperty =
        DependencyProperty.Register("ShadowOverhang", typeof(double), typeof(Shadow),
        new PropertyMetadata(OnAnyPropertyChanged));

    public static readonly DependencyProperty ShadowUnderhangProperty =
        DependencyProperty.Register("ShadowUnderhang", typeof(double), typeof(Shadow),
        new PropertyMetadata(OnAnyPropertyChanged));

    public static readonly DependencyProperty ShadowCornerRadiusProperty =
        DependencyProperty.Register("ShadowCornerRadius", typeof(double), typeof(Shadow),
        new PropertyMetadata(OnAnyPropertyChanged));

    public static readonly DependencyProperty ShadowDarknessProperty =
        DependencyProperty.Register("ShadowDarkness", typeof(double), typeof(Shadow),
        new PropertyMetadata(OnAnyPropertyChanged));

    public static readonly DependencyProperty ShadowDepthProperty =
        DependencyProperty.Register("ShadowDepth", typeof(int), typeof(Shadow),
        new PropertyMetadata(OnAnyPropertyChanged));

    ColumnDefinition _shadowRightColumn;
    RowDefinition _shadowBottomRow;
    Panel _shadowParent;

    public double ShadowOverhang
    {
        get
        {
            return (double)GetValue(ShadowOverhangProperty);
        }
        set
        {
            SetValue(ShadowOverhangProperty, value);
        }
    }

    public double ShadowUnderhang
    {
        get
        {
            return (double)GetValue(ShadowUnderhangProperty);
        }
        set
        {
            SetValue(ShadowUnderhangProperty, value);
        }
    }

    public double ShadowCornerRadius
    {
        get
        {
            return (double)GetValue(ShadowCornerRadiusProperty);
        }
        set
        {
            SetValue(ShadowCornerRadiusProperty, value);
        }
    }

    public double ShadowDarkness
    {
        get
        {
            return (double)GetValue(ShadowDarknessProperty);
        }
        set
        {
            SetValue(ShadowDarknessProperty, value);
        }
    }

    public int ShadowDepth
    {
        get
        {
            return (int)GetValue(ShadowDepthProperty);
        }
        set
        {
            SetValue(ShadowDepthProperty, value);
        }
    }

    public Shadow()
    {
        DefaultStyleKey = typeof(Shadow);
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        ControlHelper.GetRequiredTemplateChild(this, ShadowRightColumnName, out _shadowRightColumn);
        ControlHelper.GetRequiredTemplateChild(this, ShadowBottomRowName, out _shadowBottomRow);
        ControlHelper.GetRequiredTemplateChild(this, ShadowParentName, out _shadowParent);


        PerformLayout();
    }

    byte[] _initialOpacities;

    // <_current*> contain values that represent what's currently displayed
    double _currentShadowDarkness; // relative alpha; 1 = what's in template
    int _currentShadowDepth; // number of shadow layers
    double _currentShadowOverhang; // left and top margin of shadow
    double _currentShadowCornerRadius; // how much shadow overhangs at right and bottom

    void PerformLayout()
    {
        // the shadow is composed of a number of stacked layers of black rounded rectangles
        // (the margin of each is inset from that of the previous) that
        // each have a specific opacity designed to produce a good shadow effect; set
        // <shadowLayers> to that collection
        UIElementCollection shadowLayers = _shadowParent.Children;

        // initialize <_initialOpacities> if needed; at the same time, initialize <_current*>,
        // which contain values that represent what's currently displayed
        int index;
        if (_initialOpacities == null)
        {
            // initialize <_initialOpacities> to the initial values in the template
            _initialOpacities = new byte[shadowLayers.Count];
            index = 0;
            foreach (Rectangle shadowLayer in shadowLayers)
            {
                SolidColorBrush brush = (SolidColorBrush)shadowLayer.Fill;
                _initialOpacities[index++] = brush.Color.A;
            }

            // initialize <_current*> as described above
            _currentShadowDarkness = 1; // i.e. opacities are equal to <_initialOpacities>
            _currentShadowDepth = shadowLayers.Count;
            _currentShadowOverhang = shadowLayers.Count;
            _currentShadowCornerRadius = ((Rectangle)shadowLayers[0]).RadiusX;
        }

        // depth = desired number of shadow layers

        double shadowDarkness = ShadowDarkness;
        int shadowDepth = ShadowDepth;
        double shadowOverhang = ShadowOverhang;
        double shadowCornerRadius = ShadowCornerRadius;
        if ((_currentShadowDarkness != shadowDarkness) ||
		    (_currentShadowDepth != shadowDepth) ||
            (_currentShadowOverhang != shadowOverhang) ||
			(_currentShadowCornerRadius != shadowCornerRadius))
        {
            int effectiveDepth = Math.Min(shadowDepth, (int)Math.Ceiling(shadowOverhang));
            index = 0;
            int shadowLayerCount = shadowLayers.Count;
            foreach (Rectangle shadowLayer in shadowLayers)
            {
                if ((shadowDarkness == 0) || (index >= effectiveDepth))
                {
                    // hide the shadow layer
                    shadowLayer.Visibility = Visibility.Collapsed;
                }
                else
                {
                    // show the shadow layer and calculate its opacity based on <darkness>
                    shadowLayer.Visibility = Visibility.Visible;
                    SolidColorBrush brush = (SolidColorBrush)shadowLayer.Fill;
                    Color color = brush.Color;
                    byte newOpacity = (byte)Math.Round(Math.Max(0, Math.Min(255,
                        (_initialOpacities[index] * shadowDarkness))));
                    shadowLayer.Fill = new SolidColorBrush(Color.FromArgb(newOpacity, color.R, color.G, color.B));
					shadowLayer.RadiusX = shadowCornerRadius;
					shadowLayer.RadiusY = shadowCornerRadius;
                }
                index++;
            }
            _currentShadowDarkness = shadowDarkness;
            _currentShadowDepth = shadowDepth;
            _currentShadowOverhang = shadowOverhang;
            _currentShadowCornerRadius = shadowCornerRadius;
        }

        _shadowRightColumn.Width = new GridLength(shadowOverhang);
        _shadowBottomRow.Height = new GridLength(shadowOverhang);

        double shadowUnderhang = ShadowUnderhang;
        _shadowParent.Margin = new Thickness(shadowUnderhang, shadowUnderhang, 0, 0);
    }

    static void OnAnyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (Shadow)d;
        if (target._shadowRightColumn != null) // i.e. if OnApplyTemplate called yet
            target.PerformLayout();
    }

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }
}

}
